/*
 * Copyright Hundsun Technologies Inc. All Rights Reserved.
 *
 * Tools for change keys/values manually in config & registry center
 */

package utils

import (
	"errors"
	"fmt"
	"io/ioutil"
	"math"
	"os"
	"path/filepath"
	"strconv"

	"hundsun.com/hsl/hschain/common/types"

	"gopkg.in/yaml.v2"
	"hundsun.com/hsl/hschain/common/config"
	"hundsun.com/hsl/hschain/common/log/logging"
	"hundsun.com/hsl/hschain/common/util"
	"hundsun.com/hsl/hschain/protos/common"
)

const (
	//ConfigPrefix 配置前缀
	ConfigPrefix = "/config"
)

//GetPort 获取端口号
func GetPort(m map[string]string) string {
	port := ""
	if m != nil {
		if p, ok := m["port"]; ok {
			port = p
		}
	}
	return port
}

// ParseListenAddress 解析模块监听地址
func ParseListenAddress(conf *config.ModuleConfig) (*common.NetworkAddress, error) {
	if conf == nil {
		return nil, errors.New("nil module config")
	}
	if len(conf.TargetHost) == 0 {
		return nil, errors.New("config item 'TargetHost' is empty")
	}
	//TODO host格式校验
	// TODO 新版本不需要监听地址, 通过mq进行通信, 这里先注释掉对端口的强制检查
	if len(conf.Parameters["port"]) == 0 { // 没有配置端口说明该模块对端口依赖是非必要的
		return nil, nil
	}
	if conf.Parameters == nil /*|| len(conf.Parameters["port"]) == 0*/ {
		return nil, errors.New("config item 'port' not found or is empty")
	}

	port, err := strconv.Atoi(conf.Parameters["port"])
	if err != nil {
		return nil, err
	}
	if port < 0 || port > math.MaxUint16 {
		return nil, errors.New("port must be in the range [0,65535]")
	}
	return &common.NetworkAddress{
		Host: conf.TargetHost,
		Port: uint32(port),
	}, nil
}

// GetIntConfigParameterValue 从模块配配置的parameters配置项中获取子配置项int类型值。
// 不存在或者格式错误，则使用defaultValue。
func GetIntConfigParameterValue(conf *config.ModuleConfig, key string, defaultValue int) int {
	if conf == nil || conf.Parameters == nil || len(conf.Parameters[key]) == 0 {
		return defaultValue
	}
	val, err := strconv.Atoi(conf.Parameters[key])
	if err != nil {
		return defaultValue
	}
	return val

}

// GetStringConfigParameterValue 从模块配配置的parameters配置项中获取子配置项string类型值。
// 不存在或者格式错误，则使用defaultValue。
func GetStringConfigParameterValue(conf *config.ModuleConfig, key string, defaultValue string) string {
	if conf == nil || conf.Parameters == nil || len(conf.Parameters[key]) == 0 {
		return defaultValue
	}
	return conf.Parameters[key]

}

// GetBoolConfigParameterValue 从模块配配置的parameters配置项中获取子配置项bool类型值。
// 不存在或者格式错误，则使用defaultValue。
func GetBoolConfigParameterValue(conf *config.ModuleConfig, key string, defaultValue bool) bool {
	if conf == nil || conf.Parameters == nil || len(conf.Parameters[key]) == 0 {
		return defaultValue
	}
	val, err := strconv.ParseBool(conf.Parameters[key])
	if err != nil {
		return defaultValue
	}
	return val

}

// GetStringConfigParameterValueE 从模块配配置的parameters配置项中获取子配置项string类型值。
// 为空则返回错误
func GetStringConfigParameterValueE(conf *config.ModuleConfig, key string) (string, error) {
	if conf == nil || conf.Parameters == nil || len(conf.Parameters[key]) == 0 {
		return "", fmt.Errorf("config param '%s' cannot be empty", key)
	}
	return conf.Parameters[key], nil

}

//IntModuleConfigParam int类型的模块配置参数（模块配置中parameters配置项中的字典项）
type IntModuleConfigParam struct {
	Name         string
	DefaultValue int
}

// GetValue 从ModuleConfig获取参数值，如果找不到则返回默认值
func (i *IntModuleConfigParam) GetValue(moduleConfig *config.ModuleConfig, log logging.Log) int {
	value := GetIntConfigParameterValue(moduleConfig, i.Name, i.DefaultValue)
	if log != nil {
		log.Infof("%s: %d", i.Name, value)
	}
	return value
}

//StringModuleConfigParam string类型的模块配置参数（模块配置中parameters配置项中的字典项）
type StringModuleConfigParam struct {
	Name         string
	DefaultValue string
}

// GetValue 从ModuleConfig获取参数值，如果找不到则返回默认值
func (i *StringModuleConfigParam) GetValue(moduleConfig *config.ModuleConfig, log logging.Log) string {
	value := GetStringConfigParameterValue(moduleConfig, i.Name, i.DefaultValue)
	if log != nil {
		log.Infof("%s: %s", i.Name, value)
	}
	return value
}

//BoolModuleConfigParam bool类型的模块配置参数（模块配置中parameters配置项中的字典项）
type BoolModuleConfigParam struct {
	Name         string
	DefaultValue bool
}

// GetValue 从ModuleConfig获取参数值，如果找不到则返回默认值
func (i *BoolModuleConfigParam) GetValue(moduleConfig *config.ModuleConfig, log logging.Log) bool {
	value := GetBoolConfigParameterValue(moduleConfig, i.Name, i.DefaultValue)
	if log != nil {
		log.Infof("%s: %s", i.Name, value)
	}
	return value
}

//ParseConfigs 解析配置文件
func ParseConfigs(filePath string, log logging.Log) (*config.HostModulesConfig, error) {
	// read config file content
	fileBytes, err := ioutil.ReadFile(filePath)
	if err != nil {
		if log != nil {
			log.Errorf("Read file failed, err: %s\n", err.Error())
		}
		return nil, err
	}

	// parse yaml （默认：若配置文件中字段为空，解析后则为空""）
	var configs = &config.HostModulesConfig{}
	if err := yaml.Unmarshal(fileBytes, configs); err != nil {
		if log != nil {
			log.Errorf("Unmarshal from config file failed, err: %s\n", err.Error())
		}
		return nil, err
	}

	host, err := util.GetExternalIP()
	if err != nil {
		log.Errorf("Get External IP failed, err: %s\n", err.Error())
		return nil, err
	}

	configs.TargetHost = host.String()

	for _, config := range configs.Modules {
		config.TargetHost = configs.TargetHost
		port := GetPort(config.Parameters)
		config.ModuleID = fmt.Sprintf("/%s/%s:%s", config.ModuleName, host, port)
		config.Key = fmt.Sprintf("%s/%s%s", ConfigPrefix, host, config.ModuleID)
	}

	return configs, nil
}

//GetFilePath 获取文件路径
func GetFilePath(fileName string, log logging.Log) (string, error) {
	var filePath string

	// 在当前目录查找
	currentPath, err := os.Getwd()
	if err != nil {
		log.Errorf("Get wd failed,err: %s\n", err.Error())
		return "", err
	}
	filePath = filepath.Join(currentPath, config.DefaultCurrentConfigDirectory, fileName)

	if util.FileExists(filePath) {
		return filePath, nil
	}

	// 在环境变量下查找
	envPrefix := os.Getenv(config.DefaultEnvConfigDirectory)
	filePath = filepath.Join(envPrefix, fileName)
	if util.FileExists(filePath) {
		return filePath, nil
	}

	// 在默认路径下查找
	filePath = filepath.Join(config.DefaultConfigPath, fileName)
	if util.FileExists(filePath) {
		return filePath, nil
	}

	return "", types.ErrNotFound
}
